﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using KKdMainLib;
using MikuMikuModel.FarcPack;
using KKdBaseLib;
using KKdMainLib.IO;
using KKdA3DA = KKdMainLib.A3DA;
using KKdFARC = KKdMainLib.FARC;
using KKtMain = KKtLib.Main;
using KKtA3DA = KKtLib.A3DA.A3DA;
using System.Threading;
using MikuMikuModel.Logs;
using ft_module_parser.pdaconversion;
using KKdMainLib.IO;
using Directory = System.IO.Directory;
using File = System.IO.File;
using Path = System.IO.Path;
using MikuMikuLibrary.IO;

namespace ft_module_parser.aftconversion.divax
{
    class auth3d
    {
        public static int currentWorker = 0;
        public static int maxWorker = 1;

        public static string StrProc(string str)
        {
            if (!str.Contains("F_"))
                return str.Replace("EFFPV", "STGPV").Replace("STGPV0", "STGPV8");
            else return str;
            return str;
        }

        public static void ExtractA3D(string path, string acpath, a3ds a3db, divamodgen divamods = null)
        {
            Directory.CreateDirectory("a3d");
            //Directory.CreateDirectory("a3d\\common");

            foreach (string file in Directory.EnumerateFiles(path, "effstgpv*.farc", SearchOption.TopDirectoryOnly))
            {
                if (!file.Contains("ptc"))
                {
                    Tools.Extract(file, "a3d\\" + Path.GetFileNameWithoutExtension(file).Replace("effstgpv0", "EFFSTGPV8").ToUpper() + "\\");
                    var check = a3db.a3d_dbs.Any(c => c.rawrows.Any(d => d.Contains(Path.GetFileNameWithoutExtension(file).Replace("effstgpv", "EFFSTGPV").ToUpper())));
                    if (!check)
                    {
                        a3d newa3d = new a3d(0);
                        newa3d.id = a3db.a3d_dbs.Max(c => c.id) + 1;
                        newa3d.rawrows.Add(".value=" + Path.GetFileNameWithoutExtension(file).Replace("effstgpv", "EFFSTGPV").ToUpper());
                        a3db.a3d_dbs.Add(newa3d);
                    }
                }
            }

            foreach (string file in Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories))
            {
                File.Move(file, Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file).ToUpper().Replace("STGPV0", "STGPV8") + ".a3da");
            }

            foreach (string file in Directory.EnumerateFiles(path, "a3d_effpv8*.farc", SearchOption.TopDirectoryOnly))
            {
                Tools.Extract(file, "a3d\\" + Path.GetFileNameWithoutExtension(file).Replace("a3d_effpv", "EFFSTGPV").ToUpper() + "\\");


            }

            foreach (string file in Directory.EnumerateFiles(path, "CAMPV8*.farc", SearchOption.TopDirectoryOnly))
            {
                Tools.Extract(file, "a3d\\" + Path.GetFileNameWithoutExtension(file).ToUpper() + "\\");
                var check = a3db.a3d_dbs.Any(c => c.rawrows.Any(d => d.Contains(Path.GetFileNameWithoutExtension(file).ToUpper())));
                if (!check)
                {
                    a3d newa3d = new a3d(0);
                    newa3d.id = a3db.a3d_dbs.Max(c => c.id) + 1;
                    newa3d.rawrows.Add(".value=" + Path.GetFileNameWithoutExtension(file).ToUpper());
                    a3db.a3d_dbs.Add(newa3d);
                }
            }

            foreach (string file in Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories))
            {
                File.Move(file, Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file).ToUpper().Replace("EFFPV", "STGPV") + ".a3da");
            }
        }
        const int kkd = 5;
        public static unsafe void ConvertA3D(string path, string acpath, a3ds a3db, divamodgen divamods = null, BinaryFormat format = BinaryFormat.FT)
        {
            //maxWorker = Environment.ProcessorCount;

            if (kkd == 4)
            {
                Parallel.ForEach(Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories), new ParallelOptions { MaxDegreeOfParallelism = (maxWorker) }, file =>
                {
                    Interlocked.Increment(ref mass_convert.currentWorkerA3DA);
                    bool isDone = false;
                    while (!isDone)
                    {
                        string ext = Path.GetExtension(file);
                        string filepath = file.Replace(ext, "");
                        ext = ext.ToLower();
                        var data = File.ReadAllBytes(file);
                        try
                        {
                            Format Format = Format.FT;
                            KKdA3DA A;
                            A = new KKdA3DA();
                            A.A3DAReader(data);
                            A.Head.Format = Format;
                            var output = A.A3DAWriter();
                            byte[] hash;
                            byte[] hash2;
                            using (var md5 = System.Security.Cryptography.MD5.Create())
                            {
                                md5.TransformFinalBlock(output, 0, output.Length);
                                hash = md5.Hash;
                            }
                            A.Dispose();
                            KKdA3DA B = new KKdA3DA();
                            B.Head.Format = Format;
                            B.A3DAReader(data);
                            var output2 = B.A3DAWriter();
                            using (var md5 = System.Security.Cryptography.MD5.Create())
                            {
                                md5.TransformFinalBlock(output2, 0, output2.Length);
                                hash2 = md5.Hash;
                            }
                            B.Dispose();
                            var str1 = Tools.ByteArrayToString(hash);
                            var str2 = Tools.ByteArrayToString(hash2);
                            if (str1 != str2)
                            {
                                bool found1 = false;
                                bool found2 = false;
                                if (File.Exists("temp/" + Path.GetFileNameWithoutExtension(file) + "_" + str1))
                                {
                                    found1 = true;
                                }

                                if (File.Exists("temp/" + Path.GetFileNameWithoutExtension(file) + "_" + str2))
                                {
                                    found2 = true;
                                }
                                if (found1 && !found2)
                                {
                                    Logs.WriteLine("A3DA : Checksum failed but we found similiar file... Hope it works. " + str1, 1);
                                }
                                else
                                if (!found1 && found2)
                                {
                                    Logs.WriteLine("A3DA : Checksum failed but we found similiar file... Hope it works. " + str2, 1);
                                    output = output2;
                                }
                                else if (found1 && found2)
                                {
                                    var file1 = new FileInfo("temp/" + Path.GetFileNameWithoutExtension(file) + "_" + str1);
                                    var file2 = new FileInfo("temp/" + Path.GetFileNameWithoutExtension(file) + "_" + str1);

                                    if (file1.Length < file2.Length)
                                    {
                                        output2 = output;
                                        Logs.WriteLine("A3DA : Checksum failed but we used the larger file.... Hope it works. " + str2, 1);
                                    }
                                    else Logs.WriteLine("A3DA : Checksum failed but we used the larger file.... Hope it works. " + str1, 1);

                                }
                                else
                                {
                                    File.WriteAllBytes("temp/" + Path.GetFileNameWithoutExtension(file) + "_" + str1, output);
                                    File.WriteAllBytes("temp/" + Path.GetFileNameWithoutExtension(file) + "_" + str2, output2);
                                    throw new Exception("Sanity check failed! " + str1 + "|" + str2);
                                }
                            }
                            File.WriteAllBytes(filepath + ".a3da", output2);
                            isDone = true;
                        }
                        catch (Exception Ex)
                        {
                            Logs.WriteLine("KkdMainLib : Failed!\n" + Ex.Message + "\n" + Ex.StackTrace + "\nRetrying... " + Path.GetFileNameWithoutExtension(file));
                        }
                    }
                    string[] a3da_lines = null;
                    isDone = false;
                    while (!isDone)
                    {
                        try
                        {
                            a3da_lines = File.ReadAllLines(file);
                            isDone = true;
                        }
                        catch
                        {
                            Logs.WriteLine("A3DAReader : Failed! Retrying... " + Path.GetFileNameWithoutExtension(file));
                        }
                    }
                    isDone = false;
                    StreamWriter outp = null;
                    while (!isDone)
                    {
                        try
                        {
                            outp = new StreamWriter(file, false, Encoding.ASCII);
                            isDone = true;
                        }
                        catch
                        {
                            Logs.WriteLine("A3DAWriter : Failed! Retrying... " + Path.GetFileNameWithoutExtension(file));
                        }
                    }
                    outp.NewLine = "\n";

                    foreach (var i in a3da_lines)
                    {
                        var u = i.Replace("EFFPV", "STGPV").Replace("STGPV0", "STGPV8");
                        outp.WriteLine(u);
                    }
                    outp.Close();
                    Interlocked.Decrement(ref mass_convert.currentWorkerA3DA);
                });
            }

            if (kkd == 5)
            {
                foreach (var file in Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories))
                {
                    Interlocked.Increment(ref mass_convert.currentWorkerA3DA);
                    bool isDone = false;
                    string ext = Path.GetExtension(file);
                    string filepath = file.Replace(ext, "");
                    ext = ext.ToLower();
                    while (!isDone)
                    {
                        var data = File.ReadAllBytes(file);
                        try
                        {
                            Format Format = Format.FT;
                            if (format == BinaryFormat.F)
                                Format = Format.F;
                            KKdA3DA A;
                            A = new KKdA3DA();
                            A.A3DAReader(data);
                            A.Head.Format = Format;

                            /*
                            if (A.Data.Object != null)
                                for (int i = 0; i < A.Data.Object.Count(); i++)
                                {
                                    A.Data.Object[i].UIDName = StrProc(A.Data.Object[i].UIDName);
                                    A.Data.Object[i].Name = StrProc(A.Data.Object[i].UIDName);

                                    if (A.Data.Object[i].TexPat != null)
                                        for (int c = 0; c < A.Data.Object[i].TexPat.Count(); c++)
                                        {
                                            A.Data.Object[i].TexPat[c].Name = StrProc(A.Data.Object[i].TexPat[c].Name);
                                        }

                                    if (A.Data.Object[i].TexTrans != null)
                                        for (int c = 0; c < A.Data.Object[i].TexTrans.Count(); c++)
                                        {
                                            A.Data.Object[i].TexTrans[c].Name = StrProc(A.Data.Object[i].TexTrans[c].Name);
                                        }
                                }

                            if (A.Data.ObjectList != null)
                                for (int i = 0; i < A.Data.ObjectList.Count(); i++)
                                {
                                    A.Data.ObjectList[i] = StrProc(A.Data.ObjectList[i]);
                                }

                            if (A.Data.Ambient != null)
                                for (int i = 0; i < A.Data.Ambient.Count(); i++)
                                {
                                    A.Data.Ambient[i].Name = StrProc(A.Data.Ambient[i].Name);
                                }

                            if (A.Data.MaterialList != null)
                                for (int i = 0; i < A.Data.MaterialList.Count(); i++)
                                {
                                    A.Data.MaterialList[i].Name = StrProc(A.Data.MaterialList[i].Name);
                                    A.Data.MaterialList[i].HashName = StrProc(A.Data.MaterialList[i].HashName);
                                }

                            if (A.Data.MObjectHRC != null)
                                for (int i = 0; i < A.Data.MObjectHRC.Count(); i++)
                                {
                                    A.Data.MObjectHRC[i].Name = StrProc(A.Data.MObjectHRC[i].Name);

                                    for (int c = 0; c < A.Data.MObjectHRC[i].Instances.Count(); c++)
                                    {
                                        A.Data.MObjectHRC[i].Instances[c].Name = StrProc(A.Data.MObjectHRC[i].Instances[c].Name);
                                        A.Data.MObjectHRC[i].Instances[c].UIDName = StrProc(A.Data.MObjectHRC[i].Instances[c].UIDName);
                                    }
                                }

                            if (A.Data.ObjectHRC != null)
                                for (int i = 0; i < A.Data.ObjectHRC.Count(); i++)
                                {
                                    A.Data.ObjectHRC[i].UIDName = StrProc(A.Data.ObjectHRC[i].UIDName);
                                    A.Data.ObjectHRC[i].Name = StrProc(A.Data.ObjectHRC[i].Name);
                                }

                            if (A.Data.ObjectHRCList != null)
                                for (int i = 0; i < A.Data.ObjectHRCList.Count(); i++)
                                {
                                    A.Data.ObjectHRCList[i] = StrProc(A.Data.ObjectHRCList[i]);
                                }

                            if (A.Data.MObjectHRCList != null)
                                for (int i = 0; i < A.Data.MObjectHRCList.Count(); i++)
                                {
                                    A.Data.MObjectHRCList[i] = StrProc(A.Data.MObjectHRCList[i]);
                                }

                            A.Data._.FileName = StrProc(A.Data._.FileName);
                            */
                            var output = A.A3DAWriter();
                            byte[] hash;
                            using (var md5 = System.Security.Cryptography.MD5.Create())
                            {
                                md5.TransformFinalBlock(output, 0, output.Length);
                                hash = md5.Hash;
                            }
                            A.Dispose();
                            Logs.WriteLine("A3DA : " + Path.GetFileNameWithoutExtension(file) + " = " + Tools.ByteArrayToString(hash), 1);
                            File.WriteAllBytes(filepath + ".a3da", output);
                            isDone = true;
                        }
                        catch (Exception Ex)
                        {
                            Logs.WriteLine("KkdMainLib : Failed!\n" + Ex.Message + "\n" + Ex.StackTrace + "\nRetrying... " + Path.GetFileNameWithoutExtension(file));
                        }
                    }

                    //if (true == false)
                    {
                        string[] a3da_lines = null;
                        isDone = false;
                        while (!isDone)
                        {
                            try
                            {
                                a3da_lines = File.ReadAllLines(file);
                                isDone = true;
                            }
                            catch
                            {
                                Logs.WriteLine("A3DAReader : Failed! Retrying... " + Path.GetFileNameWithoutExtension(file));
                            }
                        }
                        isDone = false;
                        StreamWriter outp = null;
                        while (!isDone)
                        {
                            try
                            {
                                outp = new StreamWriter(file, false, Encoding.ASCII);
                                isDone = true;
                            }
                            catch
                            {
                                Logs.WriteLine("A3DAWriter : Failed! Retrying... " + Path.GetFileNameWithoutExtension(file));
                            }
                        }
                        outp.NewLine = "\n";

                        foreach (var i in a3da_lines)
                        {
                            if (!i.Contains(".name=F_"))
                            {
                                var u = i.Replace("EFFPV", "STGPV").Replace("STGPV0", "STGPV8");
                                outp.WriteLine(u);
                            }
                            else outp.WriteLine(i);
                        }
                        outp.Close();
                    }

                    Logs.WriteLine("A3DA : Processed " + Path.GetFileNameWithoutExtension(file), 0);

                    Interlocked.Decrement(ref mass_convert.currentWorkerA3DA);
                }
            }

            //if (kkd == 1)
            //{
            //    Parallel.ForEach(Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories), new ParallelOptions { MaxDegreeOfParallelism = maxWorker }, file =>
            //    {
            //        string ext = Path.GetExtension(file);
            //        string filepath = file.Replace(ext, "");
            //        ext = ext.ToLower();

            //        KKdBaseLib.Format Format = KKdBaseLib.Format.FT;
            //        KKdA3DA A = new KKdA3DA();
            //        A.A3DAReader(filepath);
            //        bool MP = false;

            //        if (!MP || Format > KKdBaseLib.Format.NULL)
            //            A.IO = KKdMainLib.IO.File.OpenWriter(filepath + ".a3da", true);
            //        if (Format > KKdBaseLib.Format.NULL)
            //        {
            //            if (A.Data.Format < KKdBaseLib.Format.F2LE)
            //                A.Data._.CompressF16 = Format == KKdBaseLib.Format.MGF ? 2 : 1;
            //            A.Data.Format = Format;
            //        }

            //        if (A.Data.Format > KKdBaseLib.Format.DT && A.Data.Format != KKdBaseLib.Format.FT)
            //        {
            //            File.WriteAllBytes(file, A.A3DCWriter());
            //        }
            //        else
            //            A.A3DAWriter();


            //        string[] a3da_lines = File.ReadAllLines(file);

            //        StreamWriter outp = new StreamWriter(file, false, Encoding.ASCII);
            //        outp.NewLine = "\n";

            //        foreach (var i in a3da_lines)
            //        {
            //            var u = i.Replace("EFFPV", "STGPV").Replace("STGPV0", "STGPV8");
            //            outp.WriteLine(u);
            //        }
            //        outp.Close();

            //    });
            //}

            //if (kkd == 3)
            //{
            //    Parallel.ForEach(Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories),
            //        new ParallelOptions { MaxDegreeOfParallelism = maxWorker },
            //        file =>
            //        {
            //            GC.Collect();
            //            GC.WaitForPendingFinalizers();

            //            KKdBaseLib.Extensions.buf = new byte[1024];
            //            KKdBaseLib.Extensions.bufPtr = KKdBaseLib.Extensions.buf.GetPtr();
            //            Format Format = Format.FT;
            //            var A = new KKdA3DA();
            //            A.A3DAReader(File.ReadAllBytes(file));
            //            A.Data._.CompressF16 = Format > Format.FT ? Format == Format.MGF ? 2 : 1 : 0;
            //            A.Data = Format;
            //            File.WriteAllBytes(file, A.A3DAWriter());
            //            new Thread(() =>
            //            {
            //                string[] a3da_lines = File.ReadAllLines(file);

            //                StreamWriter outp = new StreamWriter(file, false, Encoding.ASCII);
            //                outp.NewLine = "\n";

            //                foreach (var i in a3da_lines)
            //                {
            //                    var u = i.Replace("EFFPV", "STGPV").Replace("STGPV0", "STGPV8");
            //                    outp.WriteLine(u);
            //                }
            //                outp.Close();
            //            }).Start();

            //        });

            //    /*
            //    var lista3da = Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories);
            //    PD_Tool.Tools.A3D.Processor(lista3da.ToArray(), "3", true);
            //    GC.Collect();
            //    var listjson = Directory.EnumerateFiles("a3d\\", "*.json", SearchOption.AllDirectories);
            //    GC.Collect();
            //    PD_Tool.Tools.A3D.Processor(lista3da.ToArray(), "3", true);
            //    */
            //}


            if (kkd == 0)
            {
                Parallel.ForEach(Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories), new ParallelOptions
                { MaxDegreeOfParallelism = maxWorker }, file =>
                {
                    string ext = Path.GetExtension(file);
                    string filepath = file.Replace(ext, "");
                    ext = ext.ToLower();

                    KKtMain.Format Format = KKtMain.Format.DT;
                    KKtA3DA A = new KKtA3DA();
                    A.A3DAReader(filepath);
                    bool MP = false;

                    if (!MP || Format > KKtMain.Format.NULL)
                        A.IO = KKtLib.IO.OpenWriter(filepath + ".a3da", true);
                    if (Format > KKtMain.Format.NULL)
                    {
                        if (A.Data.Header.Format < KKtMain.Format.F2LE)
                            A.Data._.CompressF16 = Format == KKtMain.Format.MGF ? 2 : 1;
                        A.Data.Header.Format = Format;
                    }
                    if (!MP && A.Data.Header.Format > KKtMain.Format.DT && A.Data.Header.Format != KKtMain.Format.FT)
                        A.A3DCWriter(filepath);
                    else if (!MP || Format > KKtMain.Format.NULL)
                    {
                        A.A3DC = false;
                        A.A3DAWriter(filepath);
                    }
                    else
                        A.MsgPackWriter(filepath);


                    string[] a3da_lines = File.ReadAllLines(file);

                    StreamWriter outp = new StreamWriter(file, false, Encoding.ASCII);
                    outp.NewLine = "\n";

                    foreach (var i in a3da_lines)
                    {
                        var u = i.Replace("EFFPV", "STGPV").Replace("STGPV0", "STGPV8");
                        outp.WriteLine(u);
                    }
                    outp.Close();

                });
            }
        }

        public static a3ds CreateDBEntries(string acpath, a3ds a3db, divamodgen divamods, BinaryFormat format = BinaryFormat.FT)
        {
            foreach (string file in Directory.EnumerateFiles("a3d\\", "*.a3da", SearchOption.AllDirectories))
            {
                if ((divamods != null) && (file.Contains("STGPV")))
                {
                    int pvid = int.Parse(Path.GetFileName(file).Substring(5, 3));

                    if (pvid < 200)
                    {
                        pvid = pvid + 800;
                    }

                    {
                        var check2 = divamods.Divamods.Where(c => c.pvid == pvid).FirstOrDefault();
                        if (check2 == null)
                        {
                            divamods.Divamods.Add(new aftconversion.divamods(pvid));
                            Logs.WriteLine("a3da: Created new PV at id " + (pvid));
                            check2 = divamods.Divamods.Where(c => c.pvid == pvid).First();
                        }
                        check2.a3da.Add(Path.GetFileNameWithoutExtension(file));
                        Logs.WriteLine("a3da: Added a3d for PV at id " + pvid + "," + Path.GetFileNameWithoutExtension(file));
                    }
                }
                a3d newa3d;
                var lepath = Path.GetDirectoryName(file).Split('\\');

                var check = a3db.a3d_dbs.Any(c => c.rawrows.Any(d => d.Contains(".value=" + lepath[lepath.Count() - 1])));
                if (!check)
                {
                    newa3d = new a3d(0);
                    newa3d.id = a3db.a3d_dbs.Max(c => c.id) + 1;
                    newa3d.rawrows.Add(".value=" + lepath[lepath.Count() - 1]);
                    a3db.a3d_dbs.Add(newa3d);
                }

                a3dfixer a3Dfixer = new a3dfixer();
                newa3d = new a3d(0);
                newa3d.id = a3db.a3d_dbs_uid.Max(c => c.id) + 1;
                {
                    newa3d.rawrows.Add(".category=" + lepath[lepath.Count() - 1]);
                    bool isDone = false;
                    int size = 0;
                    while (!isDone)
                    {
                        try
                        {
                            size = a3Dfixer.getSize(file);
                            isDone = true;
                        }
                        catch
                        {
                            Logs.WriteLine("A3DA Read Error, retrying...");
                        }
                    }
                    newa3d.rawrows.Add(".size=" + size);
                    newa3d.rawrows.Add(".value=A " + Path.GetFileNameWithoutExtension(file).ToUpper());
                    a3db.a3d_dbs_uid.Add(newa3d);
                }
            }

            Logs.WriteLine("Packing farcs...");
            if (format != BinaryFormat.FT)
                Tools.MassPackFolders("a3d", acpath + @"\rom\auth_3d\");
            else
                Tools.MassPackFolders("a3d", acpath + @"\mdata\m999\rom\auth_3d\");

            return a3db;
        }
    }
}
